from main.model.devices.memory.continuous.memory_smart_block import MemorySmartBlock
from main.model.devices.memory.continuous.policy.first_fit import FirstFit
from main.exceptions.exception import NoIdealBlock, VirtualMemoryException

class ContinuousMemory(object):

    def __init__(self, memory, policy=None):
        self.__memory = memory; 
        self.__first_block = MemorySmartBlock(memory, 0, memory.size)
        self.__policy = policy or FirstFit()
        self.__allocated = 0

    def link(self, pcb, size):
        block = None
        try:
            block = self.policy.get_block(self.first_block, size)
        except NoIdealBlock:
            block = self.compact(size)
        block.cut_if_great_than(size)
        block.pcb = pcb #reset displacement
        self.allocated += size
        return block
    
    def compact(self, size):
        self.first_block.compact(size)
    
    def unlink(self, requested):
        free_block_size = self.free_and_merge(requested)
        self.allocated -= free_block_size
    
    
    def free_and_merge(self, requested):
        block = self.first_block
        if(block.pcb == requested):
            block_size = block.size
            block.pcb = None
            block.join_next_while_empty() #block.size is modified 
            return block_size
        while block.next:
            if(block.next.pcb == requested):
                block_size = block.next.size
                block.next.pcb = None
                block.join_next_while_empty() #block.size is modified 
                return block_size
            block = block.next
        if(block.pcb == requested):         
            block_size = block.size #explicit redundancy
            block.pcb = None
            return block_size
        raise NoIdealBlock("There is no block for requested pcb")
    
    def load(self, program, pcb):
        self.secure_load(program.size)
        block = self.link(pcb, program.size)
        self.memory.load(program.data, block.start, block.end)        
        return block.simple_block()
    
    def get(self, addess, pcb):
        displacement = self.get_block_by_pcb(pcb).displacement
        return self.memory.get(addess + displacement)
    
    def secure_load(self, size):
        if(size > self.free):
            raise VirtualMemoryException("There is no free space for load")

    def get_block_by_pcb(self, requested):
        block = self.first_block
        while block:
            if(block.pcb == requested):
                return block
            block = block.next
        raise NoIdealBlock("There is no block for requested pcb")
        
    def get_real(self, address, pcb):
        block = self.get_block_by_pcb(pcb)
        return address + block.desplacement
        
    @property
    def policy(self):
        return self.__policy
    
    @property
    def allocated(self):
        return self.__allocated
    
    @allocated.setter
    def allocated(self, value):
        self.__allocated = value
     
    @property
    def size(self):
        return self.memory.size
     
    @property
    def memory(self):
        return self.__memory
    
    @property
    def free(self):
        return self.size - self.allocated
    
    @property
    def first_block(self):
        return self.__first_block
    
    
    
    
    